<template>
  <view
    class="sv-fab"
    id="sv-fab"
    :style="fabStyle"
    :class="{ 'magnet-transition': !isMoving, 'fab-disabled': disabled }"
  >
    <view class="fab-main">
      <view
        class="fab-main-btn"
        @touchstart="touchMoveStart"
        @touchmove.stop.prevent="touchMoving"
        @touchend="touchMoveEnd"
        @mousedown="mouseMoveStart"
        @tap.stop.prevent="onAction"
      >
        <slot name="main"></slot>
      </view>
      <view class="fab-extend" v-if="extend && showExtend">
        <slot name="extend"></slot>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, computed, provide } from 'vue'

const props = defineProps({
  // 安全区域 上,右,下,左
  safeArea: {
    type: String,
    default: '0,0,0,0'
  },
  // 默认坐标 [x, y] 单位px
  defaultAxis: {
    type: Array,
    default: []
  },
  // 尺寸 单位px
  size: {
    type: Number,
    default: 50
  },
  // 是否固定（不允许移动）
  fixed: {
    type: Boolean,
    default: false
  },
  // 是否磁吸，或者指定边磁吸 trbl
  magnet: {
    type: [Boolean, String],
    default: true
  },
  // 是否支持扩展
  extend: {
    type: Boolean,
    default: true
  },
  // 是否禁用
  disabled: {
    type: Boolean,
    default: false
  }
})

const emits = defineEmits(['action', 'moveStart', 'moving', 'moveEnd'])

const systemInfo = uni.getSystemInfoSync()
const { windowWidth, windowHeight, windowTop, windowBottom } = systemInfo

// 安全区域
const safeParams = computed(() => {
  const safeList = props.safeArea.split(',').map(Number)
  if (safeList.length !== 4) {
    throw new Error('safeArea参数格式不正确')
  }
  return safeList
})

// 边界
const xRange = computed(() => {
  return [0 + safeParams.value[3], windowWidth - props.size - safeParams.value[1]]
})
const yRange = computed(() => {
  return [
    0 + safeParams.value[0] + windowTop,
    windowHeight - props.size - safeParams.value[2] + windowTop - windowBottom
  ]
})

// 坐标参数
const xAxis = ref(props.defaultAxis.length >= 2 ? props.defaultAxis[0] : xRange.value[1])
const yAxis = ref(props.defaultAxis.length >= 2 ? props.defaultAxis[1] : yRange.value[1] - 60) // 默认底部预留60px距离
const isMoving = ref(false) // 是否开始移动
const isTap = ref(false) // 是否是点击事件
const isTouch = ref(false) // 是否是触摸事件
const showExtend = ref(false) // 是否显示扩展

// 动态样式
const fabStyle = computed(() => {
  return {
    width: props.size + 'px',
    height: props.size + 'px',
    top: yAxis.value + 'px',
    left: xAxis.value + 'px'
  }
})

// 边缘距
const edge = computed(() => {
  // 边缘距上
  const edgeTop = yAxis.value - windowTop - safeParams.value[0]
  // 边缘距左
  const edgeLeft = xAxis.value - safeParams.value[3]
  // 边缘距右
  const edgeRight = windowWidth - xAxis.value - props.size - safeParams.value[1]
  // 边缘距下
  const edgeBottom = windowHeight - (yAxis.value - windowTop) - props.size - safeParams.value[2]

  // 指定磁吸边
  let edgeMagnet = []
  if (typeof props.magnet === 'boolean') {
    edgeMagnet = [edgeTop, edgeRight, edgeBottom, edgeLeft]
  } else if (typeof props.magnet === 'string') {
    if (props.magnet?.includes('t')) edgeMagnet.push(edgeTop)
    if (props.magnet?.includes('r')) edgeMagnet.push(edgeRight)
    if (props.magnet?.includes('b')) edgeMagnet.push(edgeBottom)
    if (props.magnet?.includes('l')) edgeMagnet.push(edgeLeft)
  }

  // 找出最近的边
  const minValue = Math.min(...edgeMagnet)
  // 边缘位置
  let edgePosition = ''
  // 优先级：右 > 左 > 上 > 下
  if (minValue == edgeRight) {
    edgePosition = 'right'
  } else if (minValue == edgeLeft) {
    edgePosition = 'left'
  } else if (minValue == edgeTop) {
    edgePosition = 'top'
  } else if (minValue == edgeBottom) {
    edgePosition = 'bottom'
  }

  return {
    distance: [edgeTop, edgeRight, edgeBottom, edgeLeft], // 上 右 下 左
    direction: edgePosition
  }
})
provide('E_EDGE', edge)

// 移动开始
let startTime, startX, startY
function fabMoveStart(e, type = 'touch') {
  // console.log('fabMoveStart', e)
  isMoving.value = true

  isTap.value = false
  startTime = Date.now()
  startX = (e.changedTouches?.length && e.changedTouches[0].clientX) || e.clientX
  startY = (e.changedTouches?.length && e.changedTouches[0].clientY) || e.clientY
  if (!startX || startX < 0) startX = 0
  if (!startY || startY < 0) startY = 0
  if (type == 'mouse') startY += windowTop

  emits('moveStart', { ...edge.value, position: [xAxis.value, yAxis.value] })
}
function touchMoveStart(e) {
  if (props.disabled) return // 禁用

  // console.log('touchMoveStart', e)
  fabMoveStart(e, 'touch')
  isTouch.value = true
}
function mouseMoveStart(e) {
  if (props.disabled) return // 禁用

  // 防止鼠标与触摸事件冲突
  if (isTouch.value) {
    isTouch.value = false
    return
  }
  // console.log('mouseMoveStart', e)
  fabMoveStart(e, 'mouse')
  // #ifdef H5
  document.addEventListener('mousemove', mouseMoving, true)
  document.addEventListener('mouseup', mouseMoveEnd, true)
  // #endif
}

// 移动中
function fabMove(e, type = 'touch') {
  // console.log('fabMove', e)
  if (props.fixed) return
  if (!isMoving.value) return

  let clientX = (e.changedTouches?.length && e.changedTouches[0].clientX) || e.clientX
  let clientY = (e.changedTouches?.length && e.changedTouches[0].clientY) || e.clientY
  if (!clientX || clientX < 0) clientX = 0
  if (!clientY || clientY < 0) clientY = 0
  if (type == 'touch') clientY += windowTop

  // 取中心位置
  const xPos = clientX - props.size / 2
  const yPos = clientY - props.size / 2

  // x轴边界处理
  if (xPos < xRange.value[0]) {
    xAxis.value = xRange.value[0]
  } else if (xPos > xRange.value[1]) {
    xAxis.value = xRange.value[1]
  } else {
    xAxis.value = xPos
  }
  // y轴边界处理
  if (yPos < yRange.value[0]) {
    yAxis.value = yRange.value[0]
  } else if (yPos > yRange.value[1]) {
    yAxis.value = yRange.value[1]
  } else {
    yAxis.value = yPos
  }

  emits('moving', { ...edge.value, position: [xAxis.value, yAxis.value] })
}

function touchMoving(e) {
  if (props.disabled) return // 禁用

  // console.log('touchMoving', e)
  fabMove(e, 'touch')
}
function mouseMoving(e) {
  if (props.disabled) return // 禁用

  // console.log('mouseMoving', e)
  fabMove(e, 'mouse')
}

// 移动结束
function fabMoveEnd(e, type = 'touch') {
  // console.log('fabMoveEnd', e)
  isMoving.value = false

  const endTime = Date.now()
  const duration = endTime - startTime
  let endX = (e.changedTouches?.length && e.changedTouches[0].clientX) || e.clientX
  let endY = (e.changedTouches?.length && e.changedTouches[0].clientY) || e.clientY
  if (!endX || endX < 0) endX = 0
  if (!endY || endY < 0) endY = 0
  // if (type == 'mouse') {} // fabMoveStart中已修正过，此处无需再进行修正

  const deltaX = Math.abs(endX - startX)
  const deltaY = Math.abs(endY - startY)
  // 判断是否为点击（短时间且无移动）
  if (duration < 200 && deltaX < 5 && deltaY < 5) {
    isTap.value = true
  }

  // 开始磁吸，优先级：右 > 左 > 上 > 下
  if (props.magnet) {
    // 吸右
    if (edge.value.direction?.includes('right')) {
      xAxis.value = xRange.value[1]
    }
    // 吸左
    else if (edge.value.direction?.includes('left')) {
      xAxis.value = xRange.value[0]
    }
    // 吸上
    else if (edge.value.direction?.includes('top')) {
      yAxis.value = yRange.value[0]
    }
    // 吸下
    else if (edge.value.direction?.includes('bottom')) {
      yAxis.value = yRange.value[1]
    }
  }

  emits('moveEnd', { ...edge.value, position: [xAxis.value, yAxis.value] })
}

function touchMoveEnd(e) {
  if (props.disabled) return // 禁用

  // console.log('touchMoveEnd', e)
  fabMoveEnd(e, 'touch')
}

function mouseMoveEnd(e) {
  if (props.disabled) return // 禁用

  // console.log('mouseMoveEnd', e)
  fabMoveEnd(e, 'mouse')
  // #ifdef H5
  document.removeEventListener('mousemove', mouseMoving, true)
  document.removeEventListener('mouseup', mouseMoveEnd, true)
  // #endif
}

function toggleExtend() {
  showExtend.value = !showExtend.value
}

function openExtend() {
  showExtend.value = true
}

function closeExtend() {
  showExtend.value = false
}

function onAction() {
  if (props.disabled) return // 禁用
  if (!isTap.value) return // 解决 move 事件冲突
  if (props.extend) toggleExtend()

  emits('action', showExtend.value)
}

defineExpose({
  toggleExtend,
  openExtend,
  closeExtend
})
</script>

<script>
export default {
  options: {
    styleIsolation: 'shared',
    addGlobalClass: true,
    virtualHost: true,
    multipleSlots: true
  }
}
</script>

<style lang="scss">
.sv-fab {
  position: fixed;
  z-index: 999999;

  &.magnet-transition {
    transition: left 0.3s ease, top 0.3s ease;
  }

  &.fab-disabled {
    pointer-events: none;
    opacity: 0.8;
  }

  .fab-main {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.1);
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;

    .fab-main-btn {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 9;
    }

    .fab-extend {
      position: absolute;
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
    }
  }
}
</style>
